#include "pch.h"
#include "ShadowMap.h"
#include "LightActor.h"
#include "RendererEngine.h"

namespace mini
{
	CShadowMap::CShadowMap()
	{
		mType = shadowType::Shadowmap;
		setShader(R"(resource\Material\transparentdShadowMap.ma)");
	}

	CShadowMap::~CShadowMap()
	{
	
	}

	int CShadowMap::nPassCount()
	{
		if (mpMaterialUnit)
		{
			return mpMaterialUnit->getShaderUnitSize();
		}
		return 0;
	}

	CShaderUnit* CShadowMap::passBegin(DeviceContext* pContext, int nNumber)
	{
		auto pShaderUnit = mpMaterialUnit->getShaderUnit(nNumber);
		if (nullptr != pShaderUnit)
		{
			switch (nNumber)
			{
			case 0:
			{
				drawDepth(pContext, pShaderUnit);
			}
			break;
			case 1:
			{
				drawScene(pContext, pShaderUnit);
			}
			break;
			default:
				break;
			}
		}
		return pShaderUnit;
	}

	void CShadowMap::passEnd(DeviceContext* pContext, int nNumber)
	{
		auto pShaderUnit = mpMaterialUnit->getShaderUnit(nNumber);
		if (nullptr != pShaderUnit)
		{
			if (0 == nNumber)
			{
				auto pTexture = mpLightActor->getRenderToTexturePtr();
				pTexture->endRender(pContext);
			}
		}
	}

	void CShadowMap::drawDepth(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		if (nullptr != mpLightActor)
		{
			pShaderUnit->setShader(pContext);

			auto pTexture = mpLightActor->getRenderToTexturePtr();
			pTexture->setSampler(mpMaterialUnit->getSampler(1));
						
			pShaderUnit->setViewMatrix(&mpLightActor->GetViewMatrix());
			pShaderUnit->setProjectMatrix(&mpLightActor->GetProjectionMatrix());
			pTexture->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
			pTexture->applyViewport(pContext);
		}
	}

	void CShadowMap::drawScene(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		if (nullptr != mpLightActor)
		{
			//ID3D11DepthStencilView* pDepthBuffer = nullptr;
			//pContext->OMGetRenderTargets(1, nullptr, &pDepthBuffer);
			//pContext->ClearDepthStencilView(pDepthBuffer, D3D11_CLEAR_DEPTH, 1.0f, 0);
			pShaderUnit->setShader(pContext);

			auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
			if (nullptr != pCamera)
			{				
				pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
				pShaderUnit->setProjectMatrix(&pCamera->getProjectMatrix());

				D3DXMatrixTranspose(&matrixArray[0], &mpLightActor->GetViewMatrix());
				D3DXMatrixTranspose(&matrixArray[1], &mpLightActor->GetProjectionMatrix());

				pShaderUnit->updateVSConstBuffer(1, &mpLightActor->getPosition());
				pShaderUnit->updateVSConstBuffer(2, matrixArray);


				CPoint4F color[2] = { mpLightActor->getAmbientColor(), mpLightActor->getDiffuseColor() };
				pShaderUnit->updatePSConstBuffer("LightBuffer", color);

				auto pTexture = mpLightActor->getRenderToTexturePtr();
				pTexture->setSampler(mpMaterialUnit->getSampler(1));
				pTexture->getRenderTexture()->commitToGpu(pContext, 1);
			}
		}
	}

	void CShadowMap::updateMeshMatrix(CShaderUnit* pShaderUnit, CMatrix* pMatrix)
	{
		if (nullptr != pMatrix && nullptr != pShaderUnit )
		{
			pShaderUnit->setWorldMatrix(pMatrix);
		}
	}

	//////////////////////////////////////////////////////////////////////////
	CSoftShadowMap::CSoftShadowMap()
	{
		setShader(R"(resource\Material\SoftShadow.ma)");
	}

	CSoftShadowMap::~CSoftShadowMap()
	{
		SafeDelete(mpBlackWhiteRenderTexture);
		SafeDelete(mpDownSampleTexure);
		SafeDelete(mpHorizontalTexure);
		SafeDelete(mpVerticalTexure);
		SafeDelete(mpBlurTexure);
		SafeDelete(mpMeshUnit);
	}

	CShaderUnit* CSoftShadowMap::passBegin(DeviceContext* pContext, int nNumber)
	{
		auto pShaderUnit = mpMaterialUnit->getShaderUnit(nNumber);
		if (nullptr != pShaderUnit)
		{
			switch (nNumber)
			{
			case 0:
			{
				drawDepth(pContext, pShaderUnit);
			}
			break;
			case 1:
			{
				drawBlackAndWhite(pContext, pShaderUnit);
			}
			break;
			case 2:
			{
				drawDownSample(pContext, pShaderUnit);
				return nullptr;
			}
			break;
			case 3:
			{
				_horizontalBlur(pContext, pShaderUnit);
				return nullptr;
			}
			break;
			case 4:
			{
				_verticalBlur(pContext, pShaderUnit);
				return nullptr;
			}
			break;
			case 5:
			{
				_drawBlurScreen(pContext, pShaderUnit);
				return nullptr;
			}
			break;
			case 6:
			{
				drawScene(pContext, pShaderUnit);
			}
			break;
			default:
				break;
			}
		}
		return pShaderUnit;
	}

	void CSoftShadowMap::passEnd(DeviceContext* pContext, int nNumber)
	{
		auto pShaderUnit = mpMaterialUnit->getShaderUnit(nNumber);
		if (nullptr != pShaderUnit)
		{
			switch (nNumber)
			{
			case 0:
			{
				auto pTexture = mpLightActor->getRenderToTexturePtr();
				pTexture->endRender(pContext);
			}
			break;
			case 1:
			{
				mpBlackWhiteRenderTexture->endRender(pContext);
			}
			break;
			case 2:
			{
				mpDownSampleTexure->endRender(pContext);
			}
			break;
			case 3:
			{
				mpHorizontalTexure->endRender(pContext);
			}
			break;
			case 4:
			{
				mpVerticalTexure->endRender(pContext);
			}
			break;
			case 5:
			{
				mpBlurTexure->endRender(pContext);
			}
			break;
			default:
				break;
			}
		}
	}

	bool CSoftShadowMap::initShadowTexutre(int nSize)
	{
		SafeDelete(mpBlackWhiteRenderTexture);
		mpBlackWhiteRenderTexture = new CRenderToTexture(nSize, nSize);
		if (nullptr != mpBlackWhiteRenderTexture)
		{
			if (mpBlackWhiteRenderTexture->createBuffer(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL, DXGI_FORMAT_R32G32B32A32_FLOAT))
			{
				SafeDelete(mpDownSampleTexure);
				mpDownSampleTexure = new CRenderToTexture(nSize / 2, nSize / 2);
				if (nullptr != mpDownSampleTexure)
				{
					if (mpDownSampleTexure->createBuffer(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL, DXGI_FORMAT_R32G32B32A32_FLOAT))
					{
						SafeDelete(mpHorizontalTexure);
						mpHorizontalTexure = new CRenderToTexture(nSize / 2, nSize / 2);
						if (nullptr != mpHorizontalTexure)
						{
							if (mpHorizontalTexure->createBuffer(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL, DXGI_FORMAT_R32G32B32A32_FLOAT))
							{
								SafeDelete(mpVerticalTexure);
								mpVerticalTexure = new CRenderToTexture(nSize / 2, nSize / 2);
								if (nullptr != mpVerticalTexure)
								{
									if (mpVerticalTexure->createBuffer(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL, DXGI_FORMAT_R32G32B32A32_FLOAT))
									{
										SafeDelete(mpBlurTexure);
										mpBlurTexure = new CRenderToTexture(nSize, nSize);
										if (nullptr != mpBlurTexure)
										{
											if (mpBlurTexure->createBuffer(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL, DXGI_FORMAT_R32G32B32A32_FLOAT))
											{
												mpMeshUnit = GEngine->createBillBoard(mpDownSampleTexure->getRenderTexture()->getImageWidth(),
																							  mpDownSampleTexure->getRenderTexture()->getImageHeight());
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return true;
	}

	void CSoftShadowMap::drawScene(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		if (nullptr != mpLightActor)
		{
			pShaderUnit->setShader(pContext);

			auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
			if (nullptr != pCamera)
			{
				pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
				pShaderUnit->setProjectMatrix(&pCamera->getProjectMatrix());

				pShaderUnit->updateVSConstBuffer(1, &mpLightActor->getPosition());

				CPoint4F color[2] = { mpLightActor->getAmbientColor(), mpLightActor->getDiffuseColor() };
				pShaderUnit->updatePSConstBuffer("LightBuffer", color);

				mpBlurTexure->setSampler(mpMaterialUnit->getSampler(1));
				mpBlurTexure->getRenderTexture()->commitToGpu(pContext, 1);
			}
		}
	}

	void CSoftShadowMap::drawBlackAndWhite(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		if (nullptr != mpLightActor)
		{
			pShaderUnit->setShader(pContext);

			mpBlackWhiteRenderTexture->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
			mpBlackWhiteRenderTexture->applyViewport(pContext);
		
			auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
			if (nullptr != pCamera)
			{
				pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
				pShaderUnit->setProjectMatrix(&pCamera->getProjectMatrix());

				D3DXMatrixTranspose(&matrixArray[0], &mpLightActor->GetViewMatrix());
				D3DXMatrixTranspose(&matrixArray[1], &mpLightActor->GetProjectionMatrix());

				pShaderUnit->updateVSConstBuffer(1, &mpLightActor->getPosition());
				pShaderUnit->updateVSConstBuffer(2, matrixArray);

				auto pTexture = mpLightActor->getRenderToTexturePtr();
				pTexture->setSampler(mpMaterialUnit->getSampler(1));
				pTexture->getRenderTexture()->commitToGpu(pContext, 1);
			}
		}
	}

	void CSoftShadowMap::drawDownSample(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		pShaderUnit->setShader(pContext);
		mpDownSampleTexure->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
		mpDownSampleTexure->applyViewport(pContext);

		auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
		if (nullptr != pCamera)
		{
			pShaderUnit->setWorldMatrix(&mpMeshUnit->getMatrix());
			pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
			pShaderUnit->setOrthoMatrix(&mpDownSampleTexure->getOrthoMatrix());
			
			GEngine->getDevice()->turnZBufferOff(pContext);

			mpBlackWhiteRenderTexture->setSampler(mpMaterialUnit->getSampler(1));
			mpBlackWhiteRenderTexture->getRenderTexture()->commitToGpu(pContext, 1);
			
			pShaderUnit->apply(pContext);
			mpMeshUnit->render(pContext);

			GEngine->getDevice()->turnZBufferOn(pContext);
		}
	}

	void CSoftShadowMap::_horizontalBlur(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		pShaderUnit->setShader(pContext);
		mpHorizontalTexure->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
		mpHorizontalTexure->applyViewport(pContext);

		auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
		if (nullptr != pCamera)
		{
			pShaderUnit->setWorldMatrix(&mpMeshUnit->getMatrix());
			pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
			pShaderUnit->setOrthoMatrix(&mpHorizontalTexure->getOrthoMatrix());

			CPoint4F screen = { 0 , 0, 0, 0 };
			screen.x = mpHorizontalTexure->getRenderTexture()->getImageWidth();
			pShaderUnit->updateVSConstBuffer(1, &screen);

			GEngine->getDevice()->turnZBufferOff(pContext);

			mpDownSampleTexure->setSampler(mpMaterialUnit->getSampler(1));
			mpDownSampleTexure->getRenderTexture()->commitToGpu(pContext, 1);

			pShaderUnit->apply(pContext);
			mpMeshUnit->render(pContext);

			GEngine->getDevice()->turnZBufferOn(pContext);
		}
	}

	void CSoftShadowMap::_verticalBlur(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		pShaderUnit->setShader(pContext);
		mpVerticalTexure->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
		mpVerticalTexure->applyViewport(pContext);

		auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
		if (nullptr != pCamera)
		{
			pShaderUnit->setWorldMatrix(&mpMeshUnit->getMatrix());
			pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
			pShaderUnit->setOrthoMatrix(&mpVerticalTexure->getOrthoMatrix());

			CPoint4F screen = { 0, 0, 0, 0 };
			screen.x = mpVerticalTexure->getRenderTexture()->getImageWidth();
			pShaderUnit->updateVSConstBuffer(1, &screen);

			GEngine->getDevice()->turnZBufferOff(pContext);

			mpHorizontalTexure->setSampler(mpMaterialUnit->getSampler(1));
			mpHorizontalTexure->getRenderTexture()->commitToGpu(pContext, 1);

			pShaderUnit->apply(pContext);
			mpMeshUnit->render(pContext);

			GEngine->getDevice()->turnZBufferOn(pContext);
		}
	}

	void CSoftShadowMap::_drawBlurScreen(DeviceContext* pContext, CShaderUnit* pShaderUnit)
	{
		pShaderUnit->setShader(pContext);
		mpBlurTexure->beginRender(CPoint4F(0.0, 0.0, 0.0, 1.0), pContext);
		mpBlurTexure->applyViewport(pContext);

		auto pCamera = CRendererEngine::getSinglePtr()->getCamera();
		if (nullptr != pCamera)
		{
			mpMeshUnit->setScale(CVector(2, 2, 2));
			mpMeshUnit->update(0);
			pShaderUnit->setWorldMatrix(&mpMeshUnit->getMatrix());
			pShaderUnit->setViewMatrix(&pCamera->getViewMatrix());
			pShaderUnit->setOrthoMatrix(&mpBlurTexure->getOrthoMatrix());

			GEngine->getDevice()->turnZBufferOff(pContext);

			mpVerticalTexure->setSampler(mpMaterialUnit->getSampler(1));
			mpVerticalTexure->getRenderTexture()->commitToGpu(pContext, 1);

			pShaderUnit->apply(pContext);

			mpMeshUnit->render(pContext);
			mpMeshUnit->setScale(CVector(1, 1, 1));
			mpMeshUnit->update(0);
			GEngine->getDevice()->turnZBufferOn(pContext);
		}
	}

}
